#include "HelloWindow.h"
#include <QExposeEvent>
#include <QResizeEvent>
#include <QKeyEvent>
#include <QCoreApplication>
#include <QOpenGLVertexArrayObject>
#include <QTime>
#include <QImage>
#include <QMatrix>
#include <QtMath>

HelloWindow::HelloWindow()
    : QWindow(),
      m_context(nullptr),
      m_update_pending(false),
      m_vao(nullptr),
      m_vbo(nullptr),
      m_texture{nullptr},
      m_cameraPos(0.0f, 0.0f, 3.0f),
      m_cameraFront(0.0f, 0.0f, -1.0f),
      m_cameraUp(0.0f, 1.0f, 0.0f),
      m_deltaTime(0.0f),
      m_lastFrame(0.0f),
      m_leftRight{0},
      m_upDown{0}
{
    setSurfaceType(QWindow::OpenGLSurface);
    resize(800, 600);
    setTitle("LearnOpenGL");
}

HelloWindow::~HelloWindow()
{
    delete m_vbo;
    delete m_texture[0];
    delete m_texture[1];
}

void HelloWindow::renderNow()
{
    if (!isExposed())
        return;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        m_context->makeCurrent(this);

        initializeOpenGLFunctions();

        glEnable(GL_DEPTH_TEST);

        bool result;
        m_program = new QOpenGLShaderProgram(this);
        result = m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "shader.vert");
        if (!result) { qDebug("Vertex error: %s", qPrintable(m_program->log())); }
        result = m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "shader.frag");
        if (!result) { qDebug("Fragment error: %s", qPrintable(m_program->log())); }
        result = m_program->link();
        if (!result) { qDebug("Link error: %s", qPrintable(m_program->log())); }

        float vertices[] = {
            -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
             0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
             0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
             0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
             0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
             0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
             0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
            -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

            -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

             0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
             0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
             0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
             0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
             0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
             0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
             0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
             0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
             0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
             0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
             0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
             0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
            -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
            -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
        };
        m_vao = new QOpenGLVertexArrayObject(this);
        m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
        m_texture[0] = new QOpenGLTexture(QImage("container.jpg"));
        m_texture[1] = new QOpenGLTexture(QImage(QString("awesomeface.png")).mirrored());

        m_vao->create();
        m_vbo->create();

        m_vao->bind();

        m_vbo->bind();
        m_vbo->setUsagePattern(QOpenGLBuffer::StaticDraw);
        m_vbo->allocate(vertices, sizeof(vertices));

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid *)nullptr);
        glEnableVertexAttribArray(0);

        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid *)(3*sizeof(GLfloat)));
        glEnableVertexAttribArray(1);

        m_texture[0]->bind(0);
        m_texture[0]->setWrapMode(QOpenGLTexture::Repeat);
        m_texture[0]->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);

        m_texture[1]->bind(1);
        m_texture[1]->setWrapMode(QOpenGLTexture::Repeat);
        m_texture[1]->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);

        m_program->bind();
        m_program->setUniformValue("texture1", 0);
        m_program->setUniformValue("texture2", 1);

        //m_texture->release();
        m_vbo->release();
        m_vao->release();

        auto retinaScale = devicePixelRatio();
        glViewport(0, 0,
                   width() * retinaScale,
                   height() * retinaScale);
    }
    else {
        m_context->makeCurrent(this);
    }


    float currentFrame = QTime::currentTime().msecsTo(QTime(0, 0)) / 1000.0f;
    m_deltaTime = currentFrame - m_lastFrame;
    m_lastFrame = currentFrame;

    //render
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_program->bind();
    m_vao->bind();

    const static float cameraSpeed = 2.5f;
    m_cameraPos -= (m_leftRight[0] * QVector3D::crossProduct(m_cameraFront, m_cameraUp)
                    + m_upDown[0] * m_cameraFront).normalized() * cameraSpeed * m_deltaTime;

    QMatrix4x4 view;
    view.lookAt(m_cameraPos, m_cameraPos + m_cameraFront, m_cameraUp);
    m_program->setUniformValue("view", view);

    QMatrix4x4 projection;
    projection.perspective(45.0f, static_cast<float>(width())/static_cast<float>(height()), 0.1f, 100.0f);
    m_program->setUniformValue("projection", projection);


    for (auto i = 0; i < 10; ++i) {
        static QVector3D cubePositions[] = {
            QVector3D( 0.0f,  0.0f,  0.0f),
            QVector3D( 2.0f,  5.0f, -15.0f),
            QVector3D(-1.5f, -2.2f, -2.5f),
            QVector3D(-3.8f, -2.0f, -12.3f),
            QVector3D( 2.4f, -0.4f, -3.5f),
            QVector3D(-1.7f,  3.0f, -7.5f),
            QVector3D( 1.3f, -2.0f, -2.5f),
            QVector3D( 1.5f,  2.0f, -2.5f),
            QVector3D( 1.5f,  0.2f, -1.5f),
            QVector3D(-1.3f,  1.0f, -1.5f)
        };

        QMatrix4x4 model;
        model.translate(cubePositions[i]);
        model.rotate(20.0f * i, 1.0f, 0.3f, 0.5f);
        m_program->setUniformValue("model", model);

        glDrawArrays(GL_TRIANGLES, 0, 36);
    }


    m_context->swapBuffers(this);
    renderLater();
}

void HelloWindow::renderLater()
{
    if (!m_update_pending) {
        m_update_pending = true;
        QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
    }
}

bool HelloWindow::event(QEvent *event) {
    switch (event->type()) {
    case QEvent::UpdateRequest:
        m_update_pending = false;
        renderNow();
        return true;
    default:
        return QWindow::event(event);
    }
}

void HelloWindow::exposeEvent(QExposeEvent *event)
{
    renderNow();
    return QWindow::exposeEvent(event);
}

void HelloWindow::resizeEvent(QResizeEvent *event)
{
    const auto &size = event->size();
    auto retinaScale = devicePixelRatio();
    if (m_context)
        glViewport(0, 0,
                   size.width() * retinaScale,
                   size.height() * retinaScale);
    renderNow();

    return QWindow::resizeEvent(event);
}

void HelloWindow::keyPressEvent(QKeyEvent *event)
{
    switch(event->key()) {
    case Qt::Key_A: case Qt::Key_Left: pushDirectory(m_leftRight, -1); return;
    case Qt::Key_W: case Qt::Key_Up: pushDirectory(m_upDown, 1); return;
    case Qt::Key_D: case Qt::Key_Right: pushDirectory(m_leftRight, 1); return;
    case Qt::Key_S: case Qt::Key_Down: pushDirectory(m_upDown, -1); return;
    case Qt::Key_Escape: close(); return;
    default: return QWindow::keyPressEvent(event);
    }
}

void HelloWindow::keyReleaseEvent(QKeyEvent *event)
{
    switch(event->key()) {
    case Qt::Key_A: case Qt::Key_Left: popDirectory(m_leftRight, -1); return;
    case Qt::Key_W: case Qt::Key_Up: popDirectory(m_upDown, 1); return;
    case Qt::Key_D: case Qt::Key_Right: popDirectory(m_leftRight, 1); return;
    case Qt::Key_S: case Qt::Key_Down: popDirectory(m_upDown, -1); return;
    default: return QWindow::keyPressEvent(event);
    }
}

void HelloWindow::pushDirectory(int array[2], int value)
{
    while (array[0] != value) {
        array[1] = array[0];
        array[0] = value;
    }
}

void HelloWindow::popDirectory(int array[2], int value)
{
    int i = 0;
    while (array[i] != value)
        if (++i >= 2)
            return;
    while (i < 2-1)
        array[i++] = array[i+1];
    array[i] = 0;
}
